package shark.flow.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import shark.flow.cmd.BackUserTaskCmd;
import shark.flow.constant.FlowableConstant;
import shark.flow.enums.CommentTypeEnum;
import shark.flow.model.Query;
import shark.flow.service.IFlowTaskService;
import shark.flow.vo.BackTaskVo;
import shark.flow.vo.CompleteTaskVo;
import shark.flow.vo.TaskQueryParamsVo;
import shark.flow.vo.TaskVo;

import java.util.*;

@Service
public class FlowTaskServiceImpl implements IFlowTaskService {

    @Autowired
    private TaskService taskService;

    @Autowired
    private ManagementService managementService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    @Override
    public String backToStepTask(BackTaskVo backVo) {
        Assert.notNull(backVo.getDistFlowElementId(), "回退结点id不能为空！");
        Assert.notNull(backVo.getTaskId(), "回退任务id不能为空！");
        Assert.notNull(backVo.getProcessInstanceId(), "回退流程实例id不能为空！");

        String instanceId = backVo.getProcessInstanceId();
        String taskId = backVo.getTaskId();
        String userCode = backVo.getUserCode();
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        // add Comment
        String backCommentContent ="回退到:" +task.getName() + "。";
        taskService.addComment(taskId, instanceId, CommentTypeEnum.HT.getName(), backCommentContent);

        String targetRealActivityId = managementService.executeCommand(new BackUserTaskCmd(runtimeService,
                taskId, backVo.getDistFlowElementId()));

        // 退回发起者处理,退回到发起者,默认设置任务执行人为发起者
        if (FlowableConstant.INITIATOR.equals(targetRealActivityId)) {
            String initiator =
                    runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult().getStartUserId();
            List<Task> newTasks = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list();
            for (Task newTask : newTasks) {
                // 约定：发起者节点为 __initiator__
                if (FlowableConstant.INITIATOR.equals(newTask.getTaskDefinitionKey())) {
                    if (newTask.getAssignee().equals("") || newTask.getAssignee() == null ) {
                        taskService.setAssignee(newTask.getId(), initiator);
                    }
                }
            }
        }
        return targetRealActivityId;
    }

    @Override
    public List<UserTask> getBackNodesByProcessInstanceId(String processInstanceId, String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().processDefinitionId(task.getProcessDefinitionId()).singleResult();
        Process process = repositoryService.getBpmnModel(definition.getId()).getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();

        UserTask userTask = null;
        if (flowElements != null) {
            for (FlowElement flowElement : flowElements) {
                // 类型为用户节点
                if (flowElement.getId().equals(task.getTaskDefinitionKey())) {
                    userTask = (UserTask) flowElement;
                }
            }
        }

        List<List<UserTask>> roads = this.findRoad(userTask, null, null, null);
        List<UserTask> userTaskList = new ArrayList<>();
        for (List<UserTask> road : roads) {
            if (userTaskList.size() == 0) {
                // 还没有可回退节点直接添加
                userTaskList = road;
            } else {
                // 如果已有回退节点，则比对取交集部分
                userTaskList.retainAll(road);
            }
        }
        return userTaskList;
    }

    @Override
    public boolean complete(CompleteTaskVo completeTaskVo) {
        if (completeTaskVo != null && StringUtils.isNotBlank(completeTaskVo.getTaskId())) {
            TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(completeTaskVo.getTaskId()).singleResult();
            if (task != null) {
                String taskId = task.getId();
                if (DelegationState.PENDING.equals(task.getDelegationState())) {
                    Task subTask = this.createSubTask(task, task.getParentTaskId(), completeTaskVo.getUserCode());
                    taskService.complete(subTask.getId());
                    taskId = subTask.getId();
                    taskService.resolveTask(completeTaskVo.getTaskId(), completeTaskVo.getVariables());
                } else {
                    if (MapUtils.isNotEmpty(completeTaskVo.getVariables())){
                        taskService.complete(completeTaskVo.getTaskId(), completeTaskVo.getVariables());
                    } else {
                        taskService.complete(completeTaskVo.getTaskId());
                    }
                    //taskService.setAssignee(taskId, completeTaskVo.getUserCode());
                    String parentTaskId = task.getParentTaskId();
                    if (StringUtils.isNotBlank(parentTaskId)){
                        String tableName = managementService.getTableName(TaskEntity.class);
                        String sql = "select count(1) from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";
                        long subTaskCount = taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).count();
                        if (subTaskCount == 0){
                            Task ptask = taskService.createTaskQuery().taskId(parentTaskId).singleResult();
                            taskService.resolveTask(parentTaskId);
                            if (CommentTypeEnum.HJQ.toString().equals(ptask.getScopeType())){
                                taskService.complete(parentTaskId);
                            }
                        }
                    }
                }
                return true;
            }
        }
        return false;
    }

    @Override
    public Task findTaskById(String taskId) {
        return taskService.createTaskQuery().taskId(taskId).singleResult();
    }

    @Override
    public IPage<TaskVo> getTodoTasks(TaskQueryParamsVo params) {
        Assert.notNull(params.getQuery(), "查询代办，分页Query不能为空！");

        Query query = params.getQuery();
        IPage<TaskVo> queryPage = new Page<>(query.getPageNum(), query.getPageSize());
        TaskQuery taskQuery = taskService.createTaskQuery();
        if (params.getUserCode() != null) {
            taskQuery.taskCandidateOrAssigned(params.getUserCode());
        }
        if (params.getBusinessKey() != null) {
            taskQuery.processInstanceBusinessKey(params.getBusinessKey());
        }
        List<Task> tasks = taskQuery.orderByTaskCreateTime().desc().listPage(query.getPageNum(), query.getPageSize());
        int total = taskQuery.orderByTaskCreateTime().desc().listPage(query.getPageNum(), query.getPageSize()).size();
        queryPage.setRecords(listChangeToVo(tasks));
        queryPage.setTotal(total);
        queryPage.setPages(queryPage.getPages());
        queryPage.setSize(query.getPageSize());
        queryPage.setCurrent(query.getPageNum());
        return queryPage;
    }

    @Override
    public IPage<TaskVo> getDoneTasks(TaskQueryParamsVo params, Query query) {
        Assert.notNull(params.getUserCode(), "查询已办，userId不能为空！");
        IPage<TaskVo> queryPage = new Page<>(query.getPageNum(), query.getPageSize());
        List<HistoricTaskInstance> taskList = historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(params.getUserCode())
                .listPage(query.getPageNum(), query.getPageSize());
        int total = historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(params.getUserCode()).list().size();
        List<TaskVo> taskVos = new ArrayList<>();
        for (HistoricTaskInstance instance : taskList) {
            TaskVo vo = new TaskVo();
            vo.setAssignee(instance.getAssignee());
            vo.setProcessDefinitionId(instance.getProcessDefinitionId());
            vo.setProcessInstanceId(instance.getProcessInstanceId());
            vo.setName(instance.getName());
            vo.setStartTime(instance.getCreateTime());
            vo.setTaskId(instance.getId());
            if (vo.getEndTime() != null) {
                vo.setFinished(true);
                vo.setFinishedTime(instance.getEndTime());
            }
            taskVos.add(vo);
        }
        queryPage.setRecords(taskVos);
        queryPage.setPages(queryPage.getPages());
        queryPage.setSize(query.getPageSize());
        queryPage.setTotal(total);
        return queryPage;
    }

    @Override
    public List<TaskVo> getFlowProgress(String procInsId) {
        List<TaskVo> taskVoList = new ArrayList<>();
        List<HistoricActivityInstance> hisActIns = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(procInsId)
                .orderByHistoricActivityInstanceStartTime()
                .asc().list();
        for (HistoricActivityInstance activityInstance : hisActIns) {
            if (!"sequenceFlow".equals(activityInstance.getActivityType())) {
                TaskVo taskVo = new TaskVo();
                taskVo.setAssignee(activityInstance.getAssignee());
                taskVo.setTaskId(activityInstance.getTaskId());
                List<org.flowable.engine.task.Comment> comments = taskService.getProcessInstanceComments(activityInstance.getProcessInstanceId());
                for (org.flowable.engine.task.Comment comment : comments) {
                    if (comment.getTaskId().equals(activityInstance.getTaskId())) {
                        taskVo.setComment(comment.getFullMessage());
                    }
                }
                taskVo.setFinished(!Objects.isNull(activityInstance.getEndTime()));
                taskVo.setStartTime(activityInstance.getStartTime());
                taskVo.setEndTime(activityInstance.getEndTime());
                taskVo.setProcessInstanceId(activityInstance.getProcessInstanceId());
                taskVo.setName(activityInstance.getActivityName());
                taskVoList.add(taskVo);
            }
        }
        return taskVoList;
    }

    private List<TaskVo> listChangeToVo(List<Task> tasks) {
        List<TaskVo> list = new ArrayList<>();
        for (Task task : tasks) {
            TaskVo vo = new TaskVo();
            vo.setName(task.getName());
            vo.setTaskId(task.getId());
            vo.setProcessInstanceId(task.getProcessInstanceId());
            vo.setAssignee(task.getAssignee());
            vo.setStartTime(task.getCreateTime());
            list.add(vo);
        }
        return list;
    }

    private Task createSubTask(TaskEntity ptask, String ptaskId, String assignee) {
        TaskEntity task = null;
        if (ptask != null){
            task = (TaskEntity) taskService.newTask(UUID.randomUUID().toString());
            task.setAssignee(assignee);
            task.setProcessInstanceId(ptask.getProcessInstanceId());
            task.setProcessDefinitionId(ptask.getProcessDefinitionId());
            task.setParentTaskId(ptaskId);
            task.setCategory(ptask.getCategory());
            task.setDescription(ptask.getDescription());
            task.setName(ptask.getName());
            task.setTaskDefinitionKey(ptask.getTaskDefinitionKey());
            task.setTaskDefinitionId(ptask.getTaskDefinitionId());
            task.setTenantId(ptask.getTenantId());
            task.setCreateTime(new Date());
            task.setPriority(ptask.getPriority());
            taskService.saveTask(task);
        }
        return task;
    }

    private List<List<UserTask>> findRoad(FlowElement source, List<UserTask> passRoads, Set<String> hasSequenceFlow, List<List<UserTask>> roads) {
        passRoads = passRoads == null ? new ArrayList<>() : passRoads;
        roads = roads == null ? new ArrayList<>() : roads;
        hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;

        // 如果该节点为开始节点，且存在上级子节点，则顺着上级子节点继续迭代
        if (source instanceof StartEvent && source.getSubProcess() != null) {
            roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads);
        }

        // 根据类型，获取入口连线
        List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);

        if (sequenceFlows != null && sequenceFlows.size() != 0) {
            for (SequenceFlow sequenceFlow: sequenceFlows) {
                // 如果发现连线重复，说明循环了，跳过这个循环
                if (hasSequenceFlow.contains(sequenceFlow.getId())) {
                    continue;
                }
                // 添加已经走过的连线
                hasSequenceFlow.add(sequenceFlow.getId());
                // 添加经过路线
                if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
                    passRoads.add((UserTask) sequenceFlow.getSourceFlowElement());
                }
                // 继续迭代
                roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads);
            }
        } else {
            // 添加路线
            roads.add(passRoads);
        }
        return roads;
    }

    private List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
        List<SequenceFlow> sequenceFlows = null;
        if (source instanceof FlowNode) {
            sequenceFlows = ((FlowNode) source).getIncomingFlows();
        } else if (source instanceof Gateway) {
            sequenceFlows = ((Gateway) source).getIncomingFlows();
        } else if (source instanceof SubProcess) {
            sequenceFlows = ((SubProcess) source).getIncomingFlows();
        } else if (source instanceof StartEvent) {
            sequenceFlows = ((StartEvent) source).getIncomingFlows();
        } else if (source instanceof EndEvent) {
            sequenceFlows = ((EndEvent) source).getIncomingFlows();
        }
        return sequenceFlows;
    }
}
